package com.demandbreakdown.xiyun.common.aspect;

import cn.hutool.core.util.StrUtil;
import com.demandbreakdown.xiyun.common.annotation.ApiLog;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;

/**
 * API日志切面
 *
 * @author poxz
 */
@Aspect
@Component
@Slf4j
public class ApiLogAspect {

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    /**
     * 使用ThreadLocal记录请求开始时间（纳秒），避免并发问题
     */
    private final ThreadLocal<Long> startTimeNanoHolder = new ThreadLocal<>();

    /**
     * 定义切点：所有标注了@ApiLog注解的方法
     */
    @Pointcut("@annotation(com.demandbreakdown.xiyun.common.annotation.ApiLog)")
    public void apiLogPointcut() {
    }

    /**
     * 环绕通知：记录请求日志
     *
     * @param joinPoint 切点
     * @return 方法返回值
     * @throws Throwable 异常
     */
    @Around("apiLogPointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 记录开始时间（纳秒）
        startTimeNanoHolder.set(System.nanoTime());

        // 获取方法签名和注解
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        ApiLog apiLog = method.getAnnotation(ApiLog.class);

        // 获取请求信息
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes != null ? attributes.getRequest() : null;

        // 记录请求开始日志
        logRequest(joinPoint, apiLog, request);

        Object result = null;
        try {
            // 执行目标方法
            result = joinPoint.proceed();

            // 记录成功响应日志
            logResponse(joinPoint, apiLog, result, null);

            return result;
        } catch (Throwable e) {
            // 记录异常日志
            logResponse(joinPoint, apiLog, null, e);
            throw e;
        } finally {
            // 清理ThreadLocal，防止内存泄漏
            startTimeNanoHolder.remove();
        }
    }

    /**
     * 记录请求日志
     */
    private void logRequest(ProceedingJoinPoint joinPoint, ApiLog apiLog, HttpServletRequest request) {
        try {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            String className = signature.getDeclaringTypeName();
            String methodName = signature.getName();

            // 构建日志信息
            StringBuilder logMsg = new StringBuilder();
            logMsg.append("\n========================================== API请求开始 ==========================================");

            // 操作描述
            if (StrUtil.isNotBlank(apiLog.value())) {
                logMsg.append("\n[操作]: ").append(apiLog.value());
            }

            // 请求信息
            if (request != null) {
                logMsg.append("\n[URL]: ").append(request.getMethod()).append(" ").append(request.getRequestURI());
                logMsg.append("\n[IP]: ").append(getClientIp(request));
            }

            logMsg.append("\n[方法]: ").append(className).append(".").append(methodName);

            // 请求参数
            if (apiLog.recordParams()) {
                Object[] args = joinPoint.getArgs();
                if (args != null && args.length > 0) {
                    String params = toJsonString(args);
                    // 如果需要脱敏
                    if (apiLog.desensitize()) {
                        params = desensitize(params);
                    }
                    logMsg.append("\n[参数]: ").append(params);
                }
            }

            logMsg.append("\n============================================================================================");

            log.info(logMsg.toString());
        } catch (Exception e) {
            log.warn("记录请求日志失败: {}", e.getMessage());
        }
    }

    /**
     * 记录响应日志
     */
    private void logResponse(ProceedingJoinPoint joinPoint, ApiLog apiLog, Object result, Throwable exception) {
        try {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            String methodName = signature.getName();

            // 计算执行耗时（纳秒转毫秒）
            long startTimeNano = startTimeNanoHolder.get();
            long executionTimeMs = (System.nanoTime() - startTimeNano) / 1_000_000;

            // 构建日志信息
            StringBuilder logMsg = new StringBuilder();
            logMsg.append("\n========================================== API请求结束 ==========================================");

            // 操作描述
            if (StrUtil.isNotBlank(apiLog.value())) {
                logMsg.append("\n[操作]: ").append(apiLog.value());
            }

            logMsg.append("\n[方法]: ").append(methodName);
            logMsg.append("\n[耗时]: ").append(executionTimeMs).append("ms");

            // 响应结果或异常
            if (exception != null) {
                logMsg.append("\n[状态]: 失败");
                logMsg.append("\n[异常]: ").append(exception.getClass().getSimpleName())
                        .append(" - ").append(exception.getMessage());
                log.error(logMsg.toString());
            } else {
                logMsg.append("\n[状态]: 成功");

                // 响应结果
                if (apiLog.recordResult() && result != null) {
                    String resultStr = toJsonString(result);
                    // 如果需要脱敏
                    if (apiLog.desensitize()) {
                        resultStr = desensitize(resultStr);
                    }
                    // 限制结果长度，避免日志过大
                    if (resultStr.length() > 1000) {
                        resultStr = resultStr.substring(0, 1000) + "...(已截断)";
                    }
                    logMsg.append("\n[结果]: ").append(resultStr);
                }

                logMsg.append("\n============================================================================================");
                log.info(logMsg.toString());
            }
        } catch (Exception e) {
            log.warn("记录响应日志失败: {}", e.getMessage());
        }
    }

    /**
     * 获取客户端真实IP
     */
    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
        if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        // 处理多个IP的情况（取第一个）
        if (StrUtil.isNotBlank(ip) && ip.contains(",")) {
            ip = ip.split(",")[0].trim();
        }
        return ip;
    }

    /**
     * 对象转JSON字符串
     */
    private String toJsonString(Object obj) {
        if (obj == null) {
            return "null";
        }
        try {
            return OBJECT_MAPPER.writeValueAsString(obj);
        } catch (Exception e) {
            return obj.toString();
        }
    }

    /**
     * 简单脱敏：使用正则表达式替换敏感字段
     * 匹配 "password":"xxx" 或 "token":"xxx" 等格式（大小写不敏感）
     */
    private String desensitize(String json) {
        if (StrUtil.isBlank(json)) {
            return json;
        }

        // 敏感字段关键词（小写）
        String[] sensitiveKeys = {
                "password", "pwd", "token", "secret",
                "authorization", "captcha", "code", "apikey"
        };

        String result = json;
        for (String key : sensitiveKeys) {
            // 匹配 "key":"value" 格式（大小写不敏感），替换为 "key":"******"
            result = result.replaceAll(
                    "(?i)\"[^\"]*" + key + "[^\"]*\"\\s*:\\s*\"[^\"]*\"",
                    "\"" + key + "\":\"******\""
            );
            // 匹配 "key":value 格式（无引号，大小写不敏感）
            result = result.replaceAll(
                    "(?i)\"[^\"]*" + key + "[^\"]*\"\\s*:\\s*[^,}\\]\"]+",
                    "\"" + key + "\":\"******\""
            );
        }

        return result;
    }
}

